package web

import (
	"encoding/json"
	"fmt"
	"gitee.com/lipore/plume/errors"
	"net/http"
	"strconv"
)

type RestFunc func(r *http.Request) (interface{}, error)

type RestResponse struct {
	Data   interface{} `json:"data"`
	Status string      `json:"status"`
	Msg    string      `json:"msg"`
}

type ApiStatus struct {
	code   string
	module string
}

func NewApiStatus(code string, module string) ApiStatus {
	return ApiStatus{code, module}
}

func (s *ApiStatus) toString() string {
	return fmt.Sprintf("%s-%s", s.module, s.code)
}

var UnknownError = NewApiStatus("system", "0")

func NewRestError(module string, code string, msg string, err error) *RestError {
	return &RestError{
		Status: NewApiStatus(code, module),
		Msg:    msg,
		err:    err,
	}
}

type RestError struct {
	Status ApiStatus
	Msg    string
	err    error
}

func (r *RestError) Error() string {
	if r.Msg == "" && r.err != nil {
		return r.err.Error()
	}
	return r.Msg
}

func (r *RestError) Unwrap() error {
	return r.err
}

func ResponseJson(response interface{}, statusCode int, w http.ResponseWriter) {
	responseStr, err := json.Marshal(response)
	if err != nil {
		panic(err)
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(statusCode)
	_, _ = w.Write(responseStr)
}

func RestControllerHandler(handle RestFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		response, err := handle(r)
		res := &RestResponse{Data: response, Status: "ok", Msg: ""}
		statusCode := http.StatusOK
		if err != nil {
			if rErr, ok := err.(*RestError); ok {
				res.Status = rErr.Status.toString()
			} else {
				coder := errors.ParseCoder(err)
				statusCode = coder.HTTPStatus()
				res.Status = strconv.FormatInt(int64(coder.Code()), 10)
				res.Data = nil
			}
			res.Msg = err.Error()
		}
		ResponseJson(res, statusCode, w)
	})
}

type CRUDApi interface {
	Get(r *http.Request) (interface{}, error)
	List(r *http.Request) (interface{}, error)
	Create(r *http.Request) (interface{}, error)
	Update(r *http.Request) (interface{}, error)
	Delete(r *http.Request) (interface{}, error)
}

func RegisterCRUDApi(route *Route, pathPrefix string, api CRUDApi) {
	route.RestMethodRoute(pathPrefix).GET(api.List).POST(api.Create)
	route.RestMethodRoute(fmt.Sprintf("%s/{:id}", pathPrefix)).GET(api.Get).PUT(api.Update).DELETE(api.Delete)
}
